use loco_rs::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use super::_entities::product_skus::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub product_id: String,
    pub product_image_id: String,
    pub variants: Option<serde_json::Value>,
    pub code: String,
    pub model: String,
    pub price: f64,
    pub origin_price: f64,
    pub quantity: i32,
    pub is_default: i8,
    pub position: i32,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 1, message = "Product ID must not be empty."))]
    pub product_id: String,
    #[validate(length(min = 1, message = "Product Image ID must not be empty."))]
    pub product_image_id: String,
    #[validate(length(min = 1, message = "Code must not be empty."))]
    pub code: String,
    #[validate(length(min = 1, message = "Model must not be empty."))]
    pub model: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            product_id: self.product_id.as_ref().to_owned(),
            product_image_id: self.product_image_id.as_ref().to_owned(),
            code: self.code.as_ref().to_owned(),
            model: self.model.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::product_skus::ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds a product SKU by the provided id
    ///
    /// # Errors
    ///
    /// When could not find product SKU or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let product_sku = product_skus::Entity::find()
            .filter(
                model::query::condition()
                    .eq(product_skus::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        product_sku.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds a product SKU by the provided code
    ///
    /// # Errors
    ///
    /// When could not find product SKU or DB query error
    pub async fn find_by_code(db: &DatabaseConnection, code: &str) -> ModelResult<Self> {
        let product_sku = product_skus::Entity::find()
            .filter(
                model::query::condition()
                    .eq(product_skus::Column::Code, code)
                    .build(),
            )
            .one(db)
            .await?;
        product_sku.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds product SKUs by the provided product id
    ///
    /// # Errors
    ///
    /// When could not find product SKUs or DB query error
    pub async fn find_by_product_id(
        db: &DatabaseConnection,
        product_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let product_skus = product_skus::Entity::find()
            .filter(
                model::query::condition()
                    .eq(product_skus::Column::ProductId, product_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(product_skus)
    }

    /// Asynchronously creates a product SKU and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the product SKU into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let txn = db.begin().await?;

        if product_skus::Entity::find()
            .filter(
                model::query::condition()
                    .eq(product_skus::Column::Code, &params.code)
                    .build(),
            )
            .one(&txn)
            .await?
            .is_some()
        {
            return Err(ModelError::EntityAlreadyExists {});
        }

        let product_sku = product_skus::ActiveModel {
            product_id: ActiveValue::set(params.product_id.to_string()),
            product_image_id: ActiveValue::set(params.product_image_id.to_string()),
            variants: ActiveValue::set(params.variants.clone()),
            code: ActiveValue::set(params.code.to_string()),
            model: ActiveValue::set(params.model.to_string()),
            price: ActiveValue::set(params.price),
            origin_price: ActiveValue::set(params.origin_price),
            quantity: ActiveValue::set(params.quantity),
            is_default: ActiveValue::set(params.is_default),
            position: ActiveValue::set(params.position),
            ..Default::default()
        }
        .insert(&txn)
        .await?;

        txn.commit().await?;

        Ok(product_sku)
    }
}

impl ActiveModel {
    /// Updates the product SKU's price
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_price(
        mut self,
        db: &DatabaseConnection,
        price: f64,
        origin_price: f64,
    ) -> ModelResult<Model> {
        self.price = ActiveValue::set(price);
        self.origin_price = ActiveValue::set(origin_price);
        Ok(self.update(db).await?)
    }

    /// Updates the product SKU's quantity
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_quantity(
        mut self,
        db: &DatabaseConnection,
        quantity: i32,
    ) -> ModelResult<Model> {
        self.quantity = ActiveValue::set(quantity);
        Ok(self.update(db).await?)
    }

    /// Updates the product SKU's default status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_default_status(
        mut self,
        db: &DatabaseConnection,
        is_default: bool,
    ) -> ModelResult<Model> {
        self.is_default = ActiveValue::set(if is_default { 1 } else { 0 });
        Ok(self.update(db).await?)
    }

    /// Updates the product SKU's position
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_position(
        mut self,
        db: &DatabaseConnection,
        position: i32,
    ) -> ModelResult<Model> {
        self.position = ActiveValue::set(position);
        Ok(self.update(db).await?)
    }
}
